import * as pregexp from "path-to-regexp"
import * as url from "url"
import * as qs from "querystring"
//import {CoatError, NotFoundError, InternalServerError} from "./exception"

export interface AsyncCb {
  (...context: Array<any>): any
}

export class Route {
  public method: string
  public pathname: string
  public regpath: any
  public params: {[key: string]: any}
  public cbs: Array<AsyncCb>

  constructor(method: string, pathname: string, regpath: any, params: Object) {
    this.method = method 
    this.pathname = pathname 
    this.regpath = regpath 
    this.params = params 
    this.cbs = [] 
  }

  public async exec(...context: Array<any>) {
    for (var cb of this.cbs) {
      await cb(...context)
    }
  }
}

export class Router {
  private routes: Array<Route>

  constructor() {
    this.routes = []
  }

  public on(method: string, pathname: string, cb: AsyncCb) {
    for (var route of this.routes) {
      if (route.method === method && route.pathname === pathname) {
        throw new Error("duplicate defined route")
      }
    }
    var route: Route = null
    for (var r of this.routes) {
      if (r.method === method && r.pathname === pathname) {
        route = r
      }
    }
    if (route === null) {
      var keys: Array<any> = []
      var regpath = pregexp(pathname, keys)
      var params: any = {}
      var i = 0
      var length = keys.length
      while (i < length) {
        params[keys[i].name] = i + 1
        i++
      }
      route = new Route(method, pathname, regpath, params)
      this.routes.push(route)
    }
    route.cbs.push(cb)
  }

  public head(pathname: string, cb: AsyncCb) {
    this.on("HEAD", pathname, cb)
    return this
  }

  public options(pathname: string, cb: AsyncCb) {
    this.on("OPTIONS", pathname, cb)
    return this
  }

  public get(pathname: string, cb: AsyncCb) {
    this.on("GET", pathname, cb)
    return this
  }

  public post(pathname: string, cb: AsyncCb) {
    this.on("POST", pathname, cb)
    return this
  }

  public put(pathname: string, cb: AsyncCb) {
    this.on("PUT", pathname, cb)
    return this
  }

  public delete(pathname: string, cb: AsyncCb) {
    this.on("DELETE", pathname, cb)
    return this
  }

  public find(method: string, pathname: string): [Route, any] {
    for (var route of this.routes) {
      if (route.method === method) {
        var input = route.regpath.exec(pathname)
        if (input !== null) {
          var params: any = {}
          for (var key in route.params) {
            params[key] = input[route.params[key]]
          }
          return [route, params]
        }
      }
    }
    return [null, null]
  }

  public async exec(method: string, pathname: string, ...context: Array<any>) {
    var [route, params] = this.find(method, pathname)
    if (route === null) {
      throw new Error("route not exists")
    }
    await route.exec(...context)
  }
}



